﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
using WiMo.Games.Inputs;
using WiMo.Games.Drawables;

namespace WiMo.Games.StandardInput
{
    public class InputManager : IInputManager
    {
        private Dictionary<Keys, KeyPressInfo> _keysDown = new Dictionary<Keys, KeyPressInfo>();
        private Dictionary<Keys, KeyPressInfo> _tempKeysDown = new Dictionary<Keys, KeyPressInfo>();
        private Dictionary<Keys, KeyPressInfo> _tempKeysUp = new Dictionary<Keys, KeyPressInfo>();
        private AutoResetEvent _drawEvent;
        private ICharacterBuffer _charBuffer;
        private ISpriteText _leftMenu;
        private ISpriteText _rightMenu;
        private ISpriteShape _backgroundRect;
        private bool _isMouseUp;
        private Location _clickPoint;
        private Color _backgroundColor = Color.Transparent;

        static Accelerometer.IGSensor GSensor = Accelerometer.GSensorFactory.CreateGSensor();

        public IDisplay DisplayManager { get; set; }

        public string LeftSoftButton
        {
            get { return _leftMenu.Text; }
            set { _leftMenu.Text = value; }
        }

        public string RightSoftButton
        {
            get { return _rightMenu.Text; }
            set { _rightMenu.Text = value; }
        }

        public bool UseAccelerometerAsThumbPad { get; set; }
        public Vector2 AccelerometerDPadThreshold { get; set; }
        private Rectangle _leftSoftbuttonRectangle;
        private Rectangle _rightSoftbuttonRectangle;
        private List<TouchPoint> _touchPoints = new List<TouchPoint>();
        private List<TouchPoint> _localPoints = new List<TouchPoint>();
        private IGameEngine _gameEngine;

        public InputManager(Form form, IGameEngine engine)
        {
            _charBuffer = new CharacterBuffer();
            _gameEngine = engine;

            form.KeyPreview = true;
            form.KeyDown += (object sender, KeyEventArgs e) => KeyDown((Keys)e.KeyCode);
            form.KeyUp += (object sender, KeyEventArgs e) => KeyUp((Keys)e.KeyCode);
            form.MouseDown += form_MouseDown;
            form.MouseUp += form_MouseUp;
            form.MouseMove += form_MouseMove;

            _drawEvent = new AutoResetEvent(true);

            DisplayManager = _gameEngine.Display;

            SupportAccelerometer = (InputManager.GSensor != null);
            UseAccelerometerAsThumbPad = false;
            AccelerometerDPadThreshold = new Vector2(1.5f, 1.5f);

            var softMenuWidth = DisplayManager.TargetSize.Width / 2;
            var softMenuHeight = 24;
            var softMenuTop = DisplayManager.TargetSize.Height - softMenuHeight;

            _leftSoftbuttonRectangle = new Rectangle(0, softMenuTop, softMenuWidth, softMenuHeight);
            _rightSoftbuttonRectangle = new Rectangle(softMenuWidth, softMenuTop, softMenuWidth, softMenuHeight);

            _backgroundRect = engine.AssetFactory.CreateRectangle();
            _backgroundRect.Origin = Origin.TopLeft;
            _backgroundRect.Position = new PointF(0, DisplayManager.TargetSize.Height - softMenuHeight);
            _backgroundRect.Size = new Size(DisplayManager.TargetSize.Width, softMenuHeight);
 
            _leftMenu = engine.AssetFactory.CreateText();
            _leftMenu.Position = new PointF((DisplayManager.TargetSize.Width / 4), DisplayManager.TargetSize.Height - softMenuHeight / 2);
            _leftMenu.Origin = Origin.Center;
            _leftMenu.Font = new SpriteFont { Name = "Tahoma", Size = 10.0f };
            _leftMenu.Size = _leftSoftbuttonRectangle.Size;
            _leftMenu.BackgroundColor = Color.Transparent;

            _rightMenu = engine.AssetFactory.CreateText();
            _rightMenu.Position = new PointF((3 * DisplayManager.TargetSize.Width / 4), DisplayManager.TargetSize.Height - softMenuHeight / 2);
            _rightMenu.Origin = Origin.Center;
            _rightMenu.Font = new SpriteFont { Name = "Tahoma", Size = 10.0f };
            _rightMenu.Size = _rightSoftbuttonRectangle.Size;
            _rightMenu.BackgroundColor = Color.Transparent;

            SoftMenuFontColor = Color.Black;
            MenuBarBackgroundColor = Color.Transparent;

            _isMouseUp = true;
        }

        public bool SupportMultiTouch { get { return false; } }
        public List<TouchPoint> TouchPoints { get { return _touchPoints; } }

        public Color SoftMenuFontColor
        {
            get { return _leftMenu.ForeColor; }
            set
            {
                _leftMenu.ForeColor = value;
                _rightMenu.ForeColor = value;
            }
        }
        public Color MenuBarBackgroundColor
        {
            get 
            { 
                return _backgroundColor; 
            }
            set
            {
                _backgroundColor = value;
                _backgroundRect.FillColor = value;
            }
        }

        public ISpriteFont SoftMenuFont
        {
            get
            {
                if (_leftMenu.Font == null) _leftMenu.Font = new SpriteFont { Name = "Tahoma", Size = 10.0f };
                if (_rightMenu.Font == null) _rightMenu.Font = new SpriteFont { Name = "Tahoma", Size = 10.0f };

                return _rightMenu.Font;
            }
            set
            {
                if (_rightMenu.Font != null)
                    _rightMenu.Font.Dispose();

                if (_leftMenu.Font != null)
                    _leftMenu.Font.Dispose();

                _rightMenu.Font = value;
                _leftMenu.Font = value;
            }
        }

        private bool _running = false;

        public void StopHopper()
        {
            _running = false;
        }

        public void StartHopper()
        {
            _running = true;
            new Thread(() =>
            {
                var rnd = new Random(DateTime.Now.Millisecond);
                while (_running)
                {
                    var stepType = rnd.Next(7);

                    Keys key = Keys.Escape;

                    switch (stepType)
                    {
                        case 0: key = Keys.Up; break;
                        case 1: key = Keys.Down; break;
                        case 2: key = Keys.Left; break;
                        case 3: key = Keys.Right; break;
                        case 4: key = Keys.Enter; break;
                        case 5: key = Keys.F1; break;
                        case 6: key = Keys.F2; break;
                    }
#if(DIAG)
                    System.Diagnostics.Debug.WriteLine("RANDOM KEY-> " + key.ToString());
#endif
                    if (key != Keys.Escape)
                    {
                        KeyDown(key);
                        new Thread(() =>
                        {
                            System.Threading.Thread.Sleep(1);
                            KeyUp(key);
                        }).Start();
                    }
                    else
                    {
                        _localPoints.Add(new TouchPoint(1, Convert.ToSingle(rnd.NextDouble()) * DisplayManager.Size.Width, Convert.ToSingle(rnd.NextDouble()) * DisplayManager.Size.Width, 1.0f, TouchPoint.TouchLocationState.Pressed));
                        new Thread(() =>
                        {
                            System.Threading.Thread.Sleep(1);
                            _localPoints.Add(new TouchPoint(1, Convert.ToSingle(rnd.NextDouble()) * 100.0f, Convert.ToSingle(rnd.NextDouble()) * 100.0f, 1.0f, TouchPoint.TouchLocationState.Released));
                        }).Start();
                    }

                    System.Threading.Thread.Sleep(2);
                }
            }).Start();
        }

        private InputManager() { }

        private Location ScalePoint(MouseEventArgs args)
        {
            return new Location((args.X - DisplayManager.DisplayOffset.X) / DisplayManager.ScaleFactor.X, (args.Y - DisplayManager.DisplayOffset.Y) / DisplayManager.ScaleFactor.Y);
        }
        
        void form_MouseMove(object sender, MouseEventArgs e)
        {
            _clickPoint = ScalePoint(e);
            var touchPoint = new TouchPoint(1, _clickPoint.X, _clickPoint.Y, 1.0f, TouchPoint.TouchLocationState.Moved);
            _localPoints.Add(touchPoint);
        }

        void form_MouseUp(object sender, MouseEventArgs e)
        {
#if(DIAG)
            System.Diagnostics.Debug.WriteLine("UP");
#endif
            _clickPoint = Location.Empty;
            _isMouseUp = true;

            if (_keysDown.ContainsKey(Keys.F1))
                KeyUp(Keys.F1);

            if (_keysDown.ContainsKey(Keys.F2))
                KeyUp(Keys.F2);

            var position = ScalePoint(e);

            var touchPoint = new TouchPoint(1, position.X, position.Y, 1.0f, TouchPoint.TouchLocationState.Released);
            _localPoints.Add(touchPoint);
#if(DIAG)
            System.Diagnostics.Debug.WriteLine("Mouse Up-> " + touchPoint.ToString());
#endif
        }

        void form_MouseDown(object sender, MouseEventArgs e)
        {
            _clickPoint = ScalePoint(e);
            _isMouseUp = false;

            if (_leftSoftbuttonRectangle.Contains(_clickPoint) && !string.IsNullOrEmpty(LeftSoftButton))
                KeyDown(Keys.F1);

            if (_rightSoftbuttonRectangle.Contains(_clickPoint) && !string.IsNullOrEmpty(RightSoftButton))
                KeyDown(Keys.F2);

            var touchPoint = new TouchPoint(1, _clickPoint.X, _clickPoint.Y, 1.0f, TouchPoint.TouchLocationState.Pressed);
            _localPoints.Add(touchPoint);
#if(DIAG)
            System.Diagnostics.Debug.WriteLine("Mouse Down-> " + touchPoint.ToString());
#endif
        }

        public ICharacterBuffer CharacterBuffer
        {
            get { return _charBuffer; }
        }

        internal void KeyDown(Keys key)
        {
            KeyDown(key, false);
        }

        internal void KeyDown(Keys key, bool fromAccelerometer)
        {
            _drawEvent.WaitOne();
            if (!_tempKeysDown.ContainsKey(key))
            {
#if(DIAG)
                System.Diagnostics.Debug.WriteLine("DOWN -> " + key);
#endif
                if (!_tempKeysDown.ContainsKey(key))
                    _tempKeysDown.Add(key, new KeyPressInfo(key, fromAccelerometer));
                //_charBuffer.Add(key);
            }
            _drawEvent.Set();
        }

        private static bool accLeftButtonUp = true;
        private static bool accRightButtonUp = true;
        private static bool accUpButtonUp = true;
        private static bool accDownButtonUp = true;

        public bool SupportAccelerometer { get; private set; }

        public void Update()
        {
            if(InputManager.GSensor != null)
                GVector = InputManager.GSensor.GetGVector();

            _drawEvent.WaitOne();

            foreach (var keyDown in _tempKeysDown)
            {
                if (!_keysDown.ContainsKey(keyDown.Key))
                    _keysDown.Add(keyDown.Key, keyDown.Value);
            }

            foreach (var keyUp in _tempKeysUp)
            {
#if(DIAG)
                System.Diagnostics.Debug.WriteLine("Removing Key-> " + keyUp.Key + " Contains" + _keysDown.ContainsKey(keyUp.Key));
#endif
                _keysDown.Remove(keyUp.Key);
            }

            _tempKeysDown.Clear();
            _tempKeysUp.Clear();

            _drawEvent.Set();

            _touchPoints = new List<TouchPoint>();

            foreach (var touchPoint in _localPoints)
                _touchPoints.Add(touchPoint);

            _localPoints = new List<TouchPoint>();

            if (!UseAccelerometerAsThumbPad)
                return;

            if (accLeftButtonUp && GVector.X < -AccelerometerDPadThreshold.X) { accLeftButtonUp = false; KeyDown(Keys.Left, true); }
            if (!accLeftButtonUp && GVector.X > -AccelerometerDPadThreshold.X) { accLeftButtonUp = true; KeyUp(Keys.Left, true); }

            if (accRightButtonUp && GVector.X > AccelerometerDPadThreshold.X) { accRightButtonUp = false; KeyDown(Keys.Right, true); }
            if (!accRightButtonUp && GVector.X < AccelerometerDPadThreshold.X) { accRightButtonUp = true; KeyUp(Keys.Right, true); }

            if (accUpButtonUp && GVector.Y < -AccelerometerDPadThreshold.Y) { accUpButtonUp = false; KeyDown(Keys.Up, true); }
            if (!accUpButtonUp && GVector.Y > -AccelerometerDPadThreshold.Y) { accUpButtonUp = true; KeyUp(Keys.Up, true); }

            if (accDownButtonUp && GVector.Y > AccelerometerDPadThreshold.Y) { accDownButtonUp = false; KeyDown(Keys.Down, true); }
            if (!accDownButtonUp && GVector.Y < AccelerometerDPadThreshold.Y) { accDownButtonUp = true; KeyUp(Keys.Down, true); }
        }

        public Vector3 GVector { get; private set; }

        internal void KeyUp(Keys key)
        {
            KeyUp(key, false);
        }

        internal void KeyUp(Keys key, bool fromAccelerometer)
        {
            if (key == Keys.Return || key == Keys.F1 || key == Keys.F2)
            {
                new Thread(() =>
                {
                    Thread.Sleep(200);
                    _drawEvent.WaitOne();
#if(DIAG)
                    System.Diagnostics.Debug.WriteLine("UP -> " + key);
#endif
                    if (!_tempKeysUp.ContainsKey(key))
                        _tempKeysUp.Add(key, new KeyPressInfo(key, fromAccelerometer));
                    _drawEvent.Set();
                }).Start();
            }
            else
            {
                _drawEvent.WaitOne();
#if(DIAG)
                System.Diagnostics.Debug.WriteLine("UP -> " + key);
#endif
                if (!_tempKeysUp.ContainsKey(key))
                    _tempKeysUp.Add(key, new KeyPressInfo(key));
                _drawEvent.Set();
            }
        }

        public bool IsKeyDown(Keys key)
        {
            return _keysDown.ContainsKey(key);
        }

        public bool IsKeyUp(Keys key)
        {
            return !_keysDown.ContainsKey(key);
        }

        public bool WasPressed(IInputManager lastState, Keys key)
        {
            return (IsKeyDown(key) && lastState.IsKeyUp(key));
        }

        public bool IsPressed(IInputManager lastState, Keys key, int ms, bool fromAccelerometer)
        {
            KeyPressInfo keyInfo;
            if (_keysDown.TryGetValue(key, out keyInfo))
            {
                return keyInfo.Debounce(ms) && keyInfo.FromAccelerometer == fromAccelerometer;
            }
            else
                return false;
        }

        public bool IsPressed(IInputManager lastState, Keys key, int ms)
        {
            KeyPressInfo keyInfo;
            if (_keysDown.TryGetValue(key, out keyInfo))
            {
                return keyInfo.Debounce(ms);
            }
            else
                return false;
        }

        public IInputManager GetKeysState()
        {
            return this.Clone();
        }

        private IInputManager Clone()
        {
            var inputManager = new InputManager();
            _drawEvent.WaitOne();
            foreach (var key in _keysDown)
                inputManager._keysDown.Add(key.Key, new KeyPressInfo(key.Value.Key, key.Value.Start));

            inputManager._isMouseUp = _isMouseUp;
            inputManager._clickPoint = _clickPoint;

            _drawEvent.Set();

            return inputManager;
        }

        public bool IsMouseUp
        {
            get { return _isMouseUp; }
        }


        public bool WasScreenTouched(IInputManager inputManager)
        {
            if (inputManager == null)
                return false;

            return (!_isMouseUp && inputManager.IsMouseUp);
        }

        public bool WasScreenTouched(IInputManager inputManager, Rectangle rect)
        {
            if (inputManager == null)
                return false;

            return (!_isMouseUp && inputManager.IsMouseUp && rect.Contains(_clickPoint));
        }

        public void Draw(GameTime time)
        {
            if(SoftMenuFontColor != Color.Transparent)
                DisplayManager.Add(_backgroundRect);

            if (!string.IsNullOrEmpty(LeftSoftButton))
                DisplayManager.Add(_leftMenu);

            if (!string.IsNullOrEmpty(RightSoftButton))
                DisplayManager.Add(_rightMenu);
        }
    }
}
